
require 'appwiki/base/base_controller.rb'
require 'appwiki/user/user.rb'
require 'appwiki/note/note.rb'

#
#
# This controller implements a default view and api over notes.
# These kinds of features exist:
#   + rest api to query for features
#   + rest api to post features
#   + common properties typically associated with web-apps such as title, link, description
#   + allows extended properties (although they are not indexed)
#   + notes can have an associated 'mime-type' or handler that controls their default presentation
#   + notes can belong to 'groups'
#   + notes are stored in a directed acyclic graph with each note having a unique name or slug
#   + notes also have an id
#   + notes also have permissions
#   + there is a note cms kind of view that presents a file folder view over notes
#   + intended to be subclassed
#

class NoteController < BaseController

  def whereami() [__FILE__] end

  def preload
    # find note associated with URL
    @user = nil
    @user = User.find_by_id(session[:userid]) if session[:userid]
    @note = Note.find_by_tokens(@user,@workingpath[1..-1])
    # set permissions please TODO
    @permitted = true
  end

  ########################################################################################
  # display current focus
  ########################################################################################

  #
  # fallback print a display over a note;
  # (normally the router catches note printing requests)
  #
  def index
    if !@permitted
      render :action => :reserved
      return
    end
    if !@note && @permitted
      render :action => :invent
      return
    end
    if @note.handler == "folder" || @note.handler == "note" || !@note.handler
      list
    end
    # deal with no handler ( print a warning )
    render :action => :index
  end

  def default
    # unrecognized event ( print warning )
    render :action => :default
  end

  # present a list view of notes in this area
  def list
    # an oncases handler would be nice TODO
#    if !@permitted
#      render :action => :reserved
#      return
#    end
    @page = params[:page]     # get @note_notes, @notes = paginate :notes, :per_note => 10  # paginate
    @show = params[:show]     # ?show all,related,min,tree,local
    @style = params[:style]   # ?style thumb,list
    @notes = nil
    @notes = @note.find_children if @note
    render :action => "list"
  end

  # present the actual contents of single note as text if any
  def contents
    if !@permitted
      render :action => :reserved
      return
    end
    return if !@note
    render :text => "#{@note.description}", :layout => [] if @note
    render :nothing => true
  end

  # present metadata view of a single note
  def metadata
    if !@permitted
      render :action => :reserved
      return
    end
    render :action => :metadata if @note
    render :nothing => true
  end

  # present revision history - xxx todo
  def rev
  end

  ########################################################################################
  # membership concepts
  # users can 'join' or become 'subscribers' to a note ; useful when a note is an 'area'
  ########################################################################################

  def join
    # valid user?
    if !@user
      flash[:notice => "You must be logged in to join" ]
      render :action => "error"
      return
    end
    if !@note
      flash[:notice] = "Nothing found by that name"
      render :action => "error"
      return
    end
    # already a member?
    if @note.is_a_member?(@user)
      flash[:notice] = "You are already a member"
      render :action => "error"
      return
    end
    # join
    @note.join @user
  end

  def leave
    # valid user?
    if !@user
      flash[:notice] = "You must be logged in to leave"      
      render :action => "error"
      return
    end
    if !@note
      flash[:notice] = "Nothing found by that name"
      render :action => "error"
      return
    end
    # not already a member?
    if !@note.is_a_member?(@user)
      flash[:notice] = "You are not even a member"
      render :action => "error"
      return
    end
    # leave
    @note.leave @user
  end

  def notesof
    # show notes of a named user is in
    @user = User.find_(params[:user])
    if !@user
      flash[:notice] = "No user found by that name"
      render :action => "error"
      return
    end
    @results = Note.get_memberships(@user);
  end

  def membersof
    # show members
    if !@note
      flash[:notice] = "Nothing found by that name"
      render :action => "error"
      return
    end
    @name = @note.uid
    @users = @note.get_members()
  end

  def sponsored
    @user = User.find_named(params[:user])
    if @user
      @results = Note.find(:all, :conditions => ["sponsor = ?", @user.id], :order => "created_at DESC", :offset => 0, :limit => 10)
    else
      @results = []
    end
  end

  ####################################################################################
  # construction utilities
  ####################################################################################

  def make_area
    @uid = @params[:uid]
    if Note.find_by_uid(@uid) || User.find_by_login(@uid)
      @flash[:warning] = "Sorry this area already exists"
      render :action => :index
      return
    end
    @path = Note.make_path(@user,[@uid],@params)
    @note = nil
    @note = @path.last if @path
    redirect_to :url => @note.relative_url if @note
  end

  # make or find a note on demand and then show it
  def make
    @uid = @params[:uid]
    return nil if !@uid || !@uid.length
    puts "uid is #{@uid}"
    @path = Note.make_path(@user,[@uid],@params)
    @note = nil
    @note = @path.last if @path
    puts "made #{@note.uid}"
    redirect_to :url => @note.relative_url if @note
  end

  # create a new note as a child of our current focus
  def create
    # must be logged in
    if !@user || !@user.id
      flash[:warning] = "must be logged in"
      render :action => :error
      return
    end
    # build a temporary new note
    @note = Note.new({:sponsor=>@user.id,:parent=>@note.id})
    # then allow edit to build a display over the subject matter
    edit
  end

  # present destroy controls, destroy and present the status
  def destroy
    if @note
      @parent = @note.get_parent if @note
      @note.destroy
      redirect_to :url => "/" if !@parent
      redirect_to :url => @parent.relative_url if @parent
    end
  end

  # edit and or save
  def edit
    # must be logged in
    if !@user || !@user.id
      flash[:warning] = "must be logged in"
      render :action => :error
      return
    end
    # try hard to get the note in question by id, uid, path, parent or create if needed
    @id = nil
    @uid = nil
    @path = nil
    @parent = nil
    @id = params[:id].to_i if params[:id] && params[:id].to_s.length > 0 && params[:id].to_i > 0
    @uid = params[:uid].to_s if params[:uid] && params[:uid].to_s.length > 0
    @path = params[:path].to_s if params[:path] && params[:path].to_s.length > 0
    @parent = params[:parent].to_i if params[:parent] && params[:parent].to_i > 0
    @note = Note.find_by_id(@id) if !@note
    @note = Note.find_by_path(@path) if !@note && @path
    @note = Note.find(:first,:conditions=>['uid=? and parent=?',@uid,@parent]) if !@note && @uid && @parent
    # if all else fails i believe it is ok to allow a new note creation on this path
    # TODO what about creating children nodes? do i have to create parents first by hand?
    if !@note
      @note = Note.new({:sponsor=>@user.id})
      @note.uid = @workingpath.last
    end
    # set it temporarily without saving (unless post is set)
    store
    # display the note again; staying on the edit page
    @members = @note.get_members.collect {|x| x.login }.join " "
    render :action => :edit
  end

  def store

    # pull out some fields that will get trashed sadly due to our lack of a formal schema.
    @members = @params[:members]
    @depiction = @params[:depiction]
    @submit = @params[:submit]
    @json = @params[:json]

    # delete crap
    # TODO we need to have a schema to define what a note can hold more clearly.
    params.delete(:members)
    params.delete('members')
    params.delete(:depiction)
    params.delete('depiction')
    params.delete(:submit)
    params.delete('submit')
    params.delete(:json)
    params.delete('json')
    # set params and set errors
    @note.set(params,@user)
    # save?
    if @submit || @method == 'post'
      if @note.save
        # write depiction after saving
        if @depiction != nil
          depiction,@err = BaseUtils.save_file({
              :site=>@domainreversed,
              :blob=>@depiction,
              :name=>@note.uid,
              :thumb=>true
              })
          if depiction
            @note.depiction = depiction
            @note.save
          end
        end
        # add members after saving
        if @members
          @members.split(' ').each do |m|
            @u = User.find_by_login(m)
            @note.join(@u.id) if @u
          end
        end
        if @json
          render :json => @note.to_json
          return
        end
        redirect_to :url => "#{@note.relative_url}/list"
        return
      else
        @note.errors.each { |alpha,beta| errors = "#{errors}<br/>#{alpha}, #{beta}" } 
        flash[:warning] = "Edit unsuccessful due to #{errors}"
      end
    end
  end

  def schema_to_html
    # fetch a schema and make a view; can supply instance or params i guess
    # TODO we need this to help deal with extended legal attributes on a note to emulate rdbms   
  end

  ####################################################################################
  # json support
  # TODO replace with built in json support in ruby later
  ####################################################################################

  # json emitter
  def write_json(a)
    write "(" + a.write_json_item + ")\n"
    @content_type = 'text/plain'
    @render_nothing = true
    @rendered_template = true
    @current_layout = nil
  end

  # json emitter
  def write_json_array a
    return nil if !a
    write "([\n"
    a.each do |b|
      write "{\"elem\":" + b.write_json_item + "},\n"
    end
    write "\n])\n"
    @content_type = 'text/plain'
    @render_nothing = true
    @rendered_template = true
    @current_layout = nil
  end

  # 
  # json post a new item - you supply a uid like /mystuff/area/# or /mystuff/area/itemname
  # using '#' means - autogen a new database unique id
  # TODO something unescapes inbound parameters for me... which is nice but confusing.
  # 
  def json_post
    @content_type = 'text/plain'
    @render_nothing = true
    @rendered_template = true
    @current_layout = nil
    puts "json_post: submitting #{params[:path]}" if @@debug
    path = params[:path]
    if path
      puts "json_post: path is #{path} l=#{path.length}" if @@debug
      path = path.split('/').compact()
      path.delete('')
      # you cannot make rooted nodes via json atm... fix? xxx
      if path.length > 1
        name = path.pop
        nodes = Note.make_path @user,path
        puts "json_post: making at path #{path.join('/')}" if @@debug
        if nodes
          note = nodes.last.make_child @user,params,name
          puts "json_post: made child #{note} from #{name} l=#{name.length}"
          params[:path] = path.join('/') # for call to json_query
          # it is important to do a query rather than returning the note; to get freshest order
          json_query
          return
          #write_json note if note
        end
      end
    end
    render :nothing => true
  end

  # json reflect (for debugging)
  def json_reflect
    @content_type = 'text/plain'
    @render_nothing = true
    @rendered_template = true
    @current_layout = nil
    h = {}
    params.each do |x,y|
      h[x]=y
    end
    write_json h
  end

  # present json result on current focus
  def json_get
    @content_type = 'text/plain'
    @render_nothing = true
    @rendered_template = true
    @current_layout = nil
    write_json @note if @note
    render :nothing => true
  end

  # json query
  # reverse descending order for now
  # TODO improve with more query parameters
  def json_query
    @content_type = 'text/plain'
    @render_nothing = true
    @rendered_template = true
    @current_layout = nil
    handler = nil
    newer = params[:newer]
    handler = params[:handler]
    offset = 0
    limit = 100
    offset = params[:offset] if params[:offset]
    limit = params[:limit] if params[:limit]
    begin
      newer = newer.to_i
      #newer = 0 if !newer || newer.to_i < 1
    rescue
    end
    #puts "note::json_query asking for #{params[:path]}" if @@debug
    parent = Note.find_by_path(@user,params[:path])
    return nil if !parent
    # handle different queries... ideally a more intelligent query system later xxx
    if params[:table] && params[:table] == "visitors"
      parent.activity_remember(@user.id) if @user
      users = parent.activity_get
      #puts "got users len n=#{users.length} p=#{parent.id} u=#{@user.id}"
      #write_json_array users
    else
      results = parent.find_children(offset,limit,newer,handler)
      # also tack on the parent; it could be used to store general global state
      # (note most of apps in appwiki use a special 'state' node however instead)
      results << parent;
      #puts "note::json_query got results #{results}" if @@debug
      write_json_array results
    end
  end

  # json delete
  def json_delete
    @content_type = 'text/plain'
    @render_nothing = true
    @rendered_template = true
    @current_layout = nil
    parent = Note.find_by_path(@user,params[:path])
    return nil if !parent
    # deletion guards
    return nil if !@user || @user.id != parent.sponsor
    # blow it away
    parent.destroy();
    return nil
  end

end
